#ifndef HERM_MSG_ENCODER_H
#define HERM_MSG_ENCODER_H

#include "herm/base/utils/types.h"
#include "herm/base/serializer/msg_base.h"

namespace herm{

class HERM_API DefHdrEncoder : public Encoder
{
public:
	DefHdrEncoder(char* buf, int len, uint16_t TYPE, uint16_t CODE);
	DefHdrEncoder(char* buf, int len);

	void Version(uint8_t ver);
};

template <uint16_t TYPE, uint16_t CODE, class HdrEncoder = DefHdrEncoder>
class Encoder0 : public HdrEncoder
{
public:
	Encoder0(char* buf, int len) : HdrEncoder(buf, len, TYPE, CODE)
	{
	}
};

template <class Body, uint16_t TYPE, uint16_t CODE, class HdrEncoder = DefHdrEncoder>
class Encoder1 : public HdrEncoder
{
public:
	Encoder1(char* buf, int len) : HdrEncoder(buf, len, TYPE, CODE)
	{
	}

	/**
	 * @brief Encode message to stream.
	 * @param [body] Message body.
	 * @param [sizeType] if body is std::string or std::vector, need to set sizeType&sizeVal.\n
	 *                   sizeType is body size type: 8bits/16bits etc.
	 * @param [sizeVal] sizeVal is the size of body.
	 */
	void Encode(const Body& body, int sizeType = 0, int sizeVal = 0)
	{
		HdrEncoder::EncodeSize(sizeType, sizeVal);
		*(Encoder*)this << body << endl;
	}
};

template <class Body1, class Body2, uint16_t TYPE, uint16_t CODE, 
	class HdrEncoder = DefHdrEncoder>
class Encoder2 : public HdrEncoder
{
public:
	Encoder2(char* buf, int len) : HdrEncoder(buf, len, TYPE, CODE)
	{
	}

	void Encode(
		const Body1& body1,
		const Body2& body2, 
		int sizeType1 = 0, int sizeVal1 = 0,
		int sizeType2 = 0, int sizeVal2 = 0)
	{
		Encoder& encoder = *(Encoder*)this;

		HdrEncoder::EncodeSize(sizeType1, sizeVal1);
		encoder << body1;

		HdrEncoder::EncodeSize(sizeType2, sizeVal2);
		encoder << body2 << endl;
	}
};

template <class Body1, class Body2, class Body3, uint16_t TYPE, uint16_t CODE, 
	class HdrEncoder = DefHdrEncoder>
class Encoder3 : public HdrEncoder
{
public:
	Encoder3(char* buf, int len) : HdrEncoder(buf, len, TYPE, CODE)
	{
	}

	void Encode(
		const Body1& body1, 
		const Body2& body2, 
		const Body3& body3,
		int sizeType1 = 0, int sizeVal1 = 0,
		int sizeType2 = 0, int sizeVal2 = 0,
		int sizeType3 = 0, int sizeVal3 = 0)
	{
		Encoder& encoder = *(Encoder*)this;

		HdrEncoder::EncodeSize(sizeType1, sizeVal1);
		encoder << body1;

		HdrEncoder::EncodeSize(sizeType2, sizeVal2);
		encoder << body2;

		HdrEncoder::EncodeSize(sizeType3, sizeVal3);
		encoder << body3 << endl;
	}
};

template <class Body1, class Body2, class Body3, class Body4, 
	uint16_t TYPE, uint16_t CODE, 
	class HdrEncoder = DefHdrEncoder>
class Encoder4 : public HdrEncoder
{
public:
	Encoder4(char* buf, int len) : HdrEncoder(buf, len, TYPE, CODE)
	{
	}

	void Encode(
		const Body1& body1, 
		const Body2& body2, 
		const Body3& body3,
		const Body4& body4,
		int sizeType1 = 0, int sizeVal1 = 0,
		int sizeType2 = 0, int sizeVal2 = 0,
		int sizeType3 = 0, int sizeVal3 = 0,
		int sizeType4 = 0, int sizeVal4 = 0)
	{
		Encoder& encoder = *(Encoder*)this;

		HdrEncoder::EncodeSize(sizeType1, sizeVal1);
		encoder << body1;

		HdrEncoder::EncodeSize(sizeType2, sizeVal2);
		encoder << body2;

		HdrEncoder::EncodeSize(sizeType3, sizeVal3);
		encoder << body3;

		HdrEncoder::EncodeSize(sizeType4, sizeVal4);
		encoder << body4 << endl;
	}
};

template <class Body1, class Body2, class Body3, class Body4, class Body5,
	uint16_t TYPE, uint16_t CODE, 
	class HdrEncoder = DefHdrEncoder>
class Encoder5 : public HdrEncoder
{
public:
	Encoder5(char* buf, int len) : HdrEncoder(buf, len, TYPE, CODE)
	{
	}

	void Encode(
		const Body1& body1, 
		const Body2& body2, 
		const Body3& body3,
		const Body4& body4,
		const Body5& body5,
		int sizeType1 = 0, int sizeVal1 = 0,
		int sizeType2 = 0, int sizeVal2 = 0,
		int sizeType3 = 0, int sizeVal3 = 0,
		int sizeType4 = 0, int sizeVal4 = 0,
		int sizeType5 = 0, int sizeVal5 = 0)
	{
		Encoder& encoder = *(Encoder*)this;

		HdrEncoder::EncodeSize(sizeType1, sizeVal1);
		encoder << body1;

		HdrEncoder::EncodeSize(sizeType2, sizeVal2);
		encoder << body2;

		HdrEncoder::EncodeSize(sizeType3, sizeVal3);
		encoder << body3;

		HdrEncoder::EncodeSize(sizeType4, sizeVal4);
		encoder << body4;

		HdrEncoder::EncodeSize(sizeType5, sizeVal5);
		encoder << body5 << endl;
	}
};

template <class Body1, class Body2, class Body3, class Body4, class Body5,
	class Body6, uint16_t TYPE, uint16_t CODE, 
	class HdrEncoder = DefHdrEncoder>
class Encoder6 : public HdrEncoder
{
public:
	Encoder6(char* buf, int len) : HdrEncoder(buf, len, TYPE, CODE)
	{
	}

	void Encode(
		const Body1& body1, 
		const Body2& body2, 
		const Body3& body3,
		const Body4& body4,
		const Body5& body5,
		const Body6& body6,
		int sizeType1 = 0, int sizeVal1 = 0,
		int sizeType2 = 0, int sizeVal2 = 0,
		int sizeType3 = 0, int sizeVal3 = 0,
		int sizeType4 = 0, int sizeVal4 = 0,
		int sizeType5 = 0, int sizeVal5 = 0,
		int sizeType6 = 0, int sizeVal6 = 0)
	{
		Encoder& encoder = *(Encoder*)this;

		HdrEncoder::EncodeSize(sizeType1, sizeVal1);
		encoder << body1;

		HdrEncoder::EncodeSize(sizeType2, sizeVal2);
		encoder << body2;

		HdrEncoder::EncodeSize(sizeType3, sizeVal3);
		encoder << body3;

		HdrEncoder::EncodeSize(sizeType4, sizeVal4);
		encoder << body4;

		HdrEncoder::EncodeSize(sizeType5, sizeVal5);
		encoder << body5;

		HdrEncoder::EncodeSize(sizeType6, sizeVal6);
		encoder << body6 << endl;
	}
};

template <class Body1, class Body2, class Body3, class Body4, class Body5,
	class Body6, class Body7, uint16_t TYPE, uint16_t CODE, 
	class HdrEncoder = DefHdrEncoder>
class Encoder7 : public HdrEncoder
{
public:
	Encoder7(char* buf, int len) : HdrEncoder(buf, len, TYPE, CODE)
	{
	}

	void Encode(
		const Body1& body1, 
		const Body2& body2, 
		const Body3& body3,
		const Body4& body4,
		const Body5& body5,
		const Body6& body6,
		const Body6& body7,
		int sizeType1 = 0, int sizeVal1 = 0,
		int sizeType2 = 0, int sizeVal2 = 0,
		int sizeType3 = 0, int sizeVal3 = 0,
		int sizeType4 = 0, int sizeVal4 = 0,
		int sizeType5 = 0, int sizeVal5 = 0,
		int sizeType6 = 0, int sizeVal6 = 0,
		int sizeType7 = 0, int sizeVal7 = 0)
	{
		Encoder& encoder = *(Encoder*)this;

		HdrEncoder::EncodeSize(sizeType1, sizeVal1);
		encoder << body1;

		HdrEncoder::EncodeSize(sizeType2, sizeVal2);
		encoder << body2;

		HdrEncoder::EncodeSize(sizeType3, sizeVal3);
		encoder << body3;

		HdrEncoder::EncodeSize(sizeType4, sizeVal4);
		encoder << body4;

		HdrEncoder::EncodeSize(sizeType5, sizeVal5);
		encoder << body5;

		HdrEncoder::EncodeSize(sizeType6, sizeVal6);
		encoder << body6;

		HdrEncoder::EncodeSize(sizeType7, sizeVal7);
		encoder << body7 << endl;
	}
};

template <class Body1, class Body2, class Body3, class Body4, class Body5,
	class Body6, class Body7, class Body8, uint16_t TYPE, uint16_t CODE, 
	class HdrEncoder = DefHdrEncoder>
class Encoder8 : public HdrEncoder
{
public:
	Encoder8(char* buf, int len) : HdrEncoder(buf, len, TYPE, CODE)
	{
	}

	void Encode(
		const Body1& body1, 
		const Body2& body2, 
		const Body3& body3,
		const Body4& body4,
		const Body5& body5,
		const Body6& body6,
		const Body6& body7,
		const Body6& body8,
		int sizeType1 = 0, int sizeVal1 = 0,
		int sizeType2 = 0, int sizeVal2 = 0,
		int sizeType3 = 0, int sizeVal3 = 0,
		int sizeType4 = 0, int sizeVal4 = 0,
		int sizeType5 = 0, int sizeVal5 = 0,
		int sizeType6 = 0, int sizeVal6 = 0,
		int sizeType7 = 0, int sizeVal7 = 0,
		int sizeType8 = 0, int sizeVal8 = 0)
	{
		Encoder& encoder = *(Encoder*)this;

		HdrEncoder::EncodeSize(sizeType1, sizeVal1);
		encoder << body1;

		HdrEncoder::EncodeSize(sizeType2, sizeVal2);
		encoder << body2;

		HdrEncoder::EncodeSize(sizeType3, sizeVal3);
		encoder << body3;

		HdrEncoder::EncodeSize(sizeType4, sizeVal4);
		encoder << body4;

		HdrEncoder::EncodeSize(sizeType5, sizeVal5);
		encoder << body5;

		HdrEncoder::EncodeSize(sizeType6, sizeVal6);
		encoder << body6;

		HdrEncoder::EncodeSize(sizeType7, sizeVal7);
		encoder << body7;

		HdrEncoder::EncodeSize(sizeType8, sizeVal8);
		encoder << body8 << endl;
	}
};

template <class Body1, class Body2, class Body3, class Body4, class Body5,
	class Body6, class Body7, class Body8, class Body9, uint16_t TYPE, uint16_t CODE, 
	class HdrEncoder = DefHdrEncoder>
class Encoder9 : public HdrEncoder
{
public:
	Encoder9(char* buf, int len) : HdrEncoder(buf, len, TYPE, CODE)
	{
	}

	void Encode(
		const Body1& body1, 
		const Body2& body2, 
		const Body3& body3,
		const Body4& body4,
		const Body5& body5,
		const Body6& body6,
		const Body6& body7,
		const Body6& body8,
		const Body6& body9,
		int sizeType1 = 0, int sizeVal1 = 0,
		int sizeType2 = 0, int sizeVal2 = 0,
		int sizeType3 = 0, int sizeVal3 = 0,
		int sizeType4 = 0, int sizeVal4 = 0,
		int sizeType5 = 0, int sizeVal5 = 0,
		int sizeType6 = 0, int sizeVal6 = 0,
		int sizeType7 = 0, int sizeVal7 = 0,
		int sizeType8 = 0, int sizeVal8 = 0,
		int sizeType9 = 0, int sizeVal9 = 0)
	{
		Encoder& encoder = *(Encoder*)this;

		HdrEncoder::EncodeSize(sizeType1, sizeVal1);
		encoder << body1;

		HdrEncoder::EncodeSize(sizeType2, sizeVal2);
		encoder << body2;

		HdrEncoder::EncodeSize(sizeType3, sizeVal3);
		encoder << body3;

		HdrEncoder::EncodeSize(sizeType4, sizeVal4);
		encoder << body4;

		HdrEncoder::EncodeSize(sizeType5, sizeVal5);
		encoder << body5;

		HdrEncoder::EncodeSize(sizeType6, sizeVal6);
		encoder << body6;

		HdrEncoder::EncodeSize(sizeType7, sizeVal7);
		encoder << body7;

		HdrEncoder::EncodeSize(sizeType8, sizeVal8);
		encoder << body8;

		HdrEncoder::EncodeSize(sizeType9, sizeVal9);
		encoder << body9 << endl;
	}
};
}

#endif
